package vip.liux.front.infrastructure.jpa.identity;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import vip.liux.contracts.models.identity.IdentityUser;
import vip.liux.contracts.models.identity.IdentityUserLogin;
import vip.liux.contracts.models.identity.IdentityUserLoginId;
import vip.liux.contracts.models.identity.UserLoginStore;
import vip.liux.contracts.models.identity.vo.UserLoginInfo;
import vip.liux.contracts.repositories.identity.IdentityUserRepository;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public interface IdentityUserJpaRepository extends JpaRepository<IdentityUser, Long>,
        IdentityUserRepository, JpaSpecificationExecutor<IdentityUser>, UserLoginStore<IdentityUser> {

    @Override
    default List<IdentityUser> search(String sorting, String filter, int maxResultCount, int skipCount) {
        Sort sort = Sort.unsorted();
        if (StringUtils.isNotBlank(sorting)) {
            if (StringUtils.endsWithIgnoreCase(sorting, Sort.Direction.ASC.name())) {
                sort = Sort.by(Sort.Direction.ASC, StringUtils.removeEndIgnoreCase(sorting, Sort.Direction.ASC.name()));
            } else if (StringUtils.endsWithIgnoreCase(sorting, Sort.Direction.DESC.name())) {
                sort = Sort.by(Sort.Direction.DESC, StringUtils.removeEndIgnoreCase(sorting, Sort.Direction.DESC.name()));
            } else {
                sort = Sort.by(Sort.Direction.ASC, sorting);
            }
        }
        return findAll((r, q, c) -> {
            List<Predicate> predicates = new java.util.ArrayList<>();
            if (StringUtils.isNotBlank(filter)) {
                predicates.add(
                        c.or(
                                c.like(r.get("email"), "%" + filter + "%"),
                                c.like(r.get("phoneNumber"), "%" + filter + "%"),
                                c.like(r.get("name"), "%" + filter + "%")
                        )
                );
            }
            return c.and(predicates.toArray(new Predicate[0]));
        }, PageRequest.of(skipCount / maxResultCount, maxResultCount, sort)).getContent();
    }

    @Override
    default long count(String filter) {
        return count((r, q, c) -> {
            List<Predicate> predicates = new java.util.ArrayList<>();
            if (StringUtils.isNotBlank(filter)) {
                predicates.add(
                        c.or(
                                c.like(r.get("email"), "%" + filter + "%"),
                                c.like(r.get("phoneNumber"), "%" + filter + "%"),
                                c.like(r.get("name"), "%" + filter + "%")
                        )
                );
            }
            return c.and(predicates.toArray(new Predicate[0]));
        });
    }

    @Query("SELECT u FROM IdentityUser u WHERE u.id IN :ids")
    @Override
    List<IdentityUser> getList(Collection<Long> ids);


    @PersistenceContext
    EntityManager entityManager = null;

    @Override
    default void addLogin(IdentityUser user, UserLoginInfo login) {
        if (user == null) {
            throw new IllegalArgumentException("user must not be null");
        }
        if (login == null) {
            throw new IllegalArgumentException("login must not be null");
        }
        saveLogin(createUserLogin(user, login));
    }

    @Override
    default void removeLogin(IdentityUser user, String loginProvider, String providerKey) {
        if (user == null) {
            throw new IllegalArgumentException("user must not be null");
        }
        Optional<IdentityUserLogin> optional = findUserLogin(user.getId(), loginProvider, providerKey);
        optional.ifPresent(this::removeLogin);
    }

    @Override
    default List<UserLoginInfo> getLogins(IdentityUser user) {
        if (user == null) {
            throw new IllegalArgumentException("user must not be null");
        }
        Long userId = user.getId();
        return findLogin(userId);
    }

    @Override
    default Optional<IdentityUser> findByLogin(String loginProvider, String providerKey) {
        Optional<IdentityUserLogin> userLogin = findUserLogin(loginProvider, providerKey);
        if (userLogin.isPresent()) {
            return findUser(Objects.requireNonNull(userLogin.get().getId()).getUserId());
        }
        return Optional.empty();
    }

    EntityManager getEntityManager();

    void setEntityManager(EntityManager entityManager);

    @Query("select u from IdentityUser u where u.id = :userId")
    Optional<IdentityUser> findUser(Long userId);

    @Query("select l from IdentityUserLogin l where l.id.userId = :id and l.id.loginProvider = :loginProvider and l.providerKey = :providerKey")
    Optional<IdentityUserLogin> findUserLogin(Long id, String loginProvider, String providerKey);

    @Query("select l from IdentityUserLogin l where l.id.loginProvider = :loginProvider and l.providerKey = :providerKey")
    Optional<IdentityUserLogin> findUserLogin(String loginProvider, String providerKey);

    @Query("select l from IdentityUserLogin l where l.id.userId = :i")
    List<UserLoginInfo> findLogin(Long userId);

    default void removeLogin(IdentityUserLogin login) {
        EntityManager em = getEntityManager();
        em.detach(login);
    }

    default void saveLogin(IdentityUserLogin userLogin) {
        EntityManager entityManager = getEntityManager();
        entityManager.merge(userLogin);
    }

    default IdentityUserLogin createUserLogin(IdentityUser user, UserLoginInfo login) {
        IdentityUserLogin userLogin = new IdentityUserLogin();
        IdentityUserLoginId id = new IdentityUserLoginId();
        id.setLoginProvider(login.getLoginProvider());
        userLogin.setUser(user);
        userLogin.setId(id);
        userLogin.setProviderKey(login.getProviderKey());
        userLogin.setProviderDisplayName(login.getProviderDisplayName());
        return userLogin;
    }
}
